﻿// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
//     <version>$Revision$</version>
// </file>

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor.Util;

namespace ICSharpCode.TextEditor
{
	public class TextAreaClipboardHandler
	{
		TextArea textArea;
		
		public bool EnableCut {
			get {
				return textArea.EnableCutOrPaste; //textArea.SelectionManager.HasSomethingSelected;
			}
		}
		
		public bool EnableCopy {
			get {
				return true; //textArea.SelectionManager.HasSomethingSelected;
			}
		}
		
		public delegate bool ClipboardContainsTextDelegate();
		
		/// <summary>
		/// Is called when CachedClipboardContainsText should be updated.
		/// If this property is null (the default value), the text editor uses
		/// System.Windows.Forms.Clipboard.ContainsText.
		/// </summary>
		/// <remarks>
		/// This property is useful if you want to prevent the default Clipboard.ContainsText
		/// behaviour that waits for the clipboard to be available - the clipboard might
		/// never become available if it is owned by a process that is paused by the debugger.
		/// </remarks>
		public static ClipboardContainsTextDelegate GetClipboardContainsText;
		
		public bool EnablePaste {
			get {
				if (!textArea.EnableCutOrPaste)
					return false;
				ClipboardContainsTextDelegate d = GetClipboardContainsText;
				if (d != null) {
					return d();
				} else {
					try {
						return Clipboard.ContainsText();
					} catch (ExternalException) {
						return false;
					}
				}
			}
		}
		
		public bool EnableDelete {
			get {
				return textArea.SelectionManager.HasSomethingSelected && !textArea.SelectionManager.SelectionIsReadonly;
			}
		}
		
		public bool EnableSelectAll {
			get {
				return true;
			}
		}
		
		public TextAreaClipboardHandler(TextArea textArea)
		{
			this.textArea = textArea;
			textArea.SelectionManager.SelectionChanged += new EventHandler(DocumentSelectionChanged);
		}
		
		void DocumentSelectionChanged(object sender, EventArgs e)
		{
//			((DefaultWorkbench)WorkbenchSingleton.Workbench).UpdateToolbars();
		}
                
		const string LineSelectedType = "MSDEVLineSelect";  // This is the type VS 2003 and 2005 use for flagging a whole line copy
        const string MuiltSelectedType = "MSDEVLinesSelect"; // Like more than vs 2013 use for flaggin muilts lines selected editing

        bool CopyTextToClipboard(string stringToCopy, bool asLine)
        {
            if (stringToCopy.Length > 0)
            {
                DataObject dataObject = new DataObject();
                dataObject.SetData(DataFormats.UnicodeText, true, stringToCopy);
                if (asLine)
                {
                    MemoryStream lineSelected = new MemoryStream(1);
                    lineSelected.WriteByte(1);
                    dataObject.SetData(LineSelectedType, false, lineSelected);
                }
                // Default has no highlighting, therefore we don't need RTF output
                if (textArea.Document.HighlightingStrategy.Name != "Default")
                {
                    dataObject.SetData(DataFormats.Rtf, RtfWriter.GenerateRtf(textArea));
                    dataObject.SetData(DataFormats.Html, HtmlWriter.GenerateHtml(textArea));
                }
                if (textArea.SelectionManager.IsMutilSelect)
                {
                    int max = 0;  
                    string selecteText = "";
                    foreach (ISelection selection in textArea.SelectionManager.selectionCollection)
                    {
                        string text = selection.SelectedText;
                        int columnCount = selection.EndPosition.Column - selection.StartPosition.Column;
                        if (max <= columnCount)
                        {
                            max = columnCount;
                        }
                        selecteText += text;                       
                    }
                    selecteText += "__maxLength__=" + max;
                    dataObject.SetData(MuiltSelectedType, selecteText);
                }
                OnCopyText(new CopyTextEventArgs(stringToCopy));

                SafeSetClipboard(dataObject);
                return true;
            }
            else
            {
                return false;
            }
        }
		
		// Code duplication: TextAreaClipboardHandler.cs also has SafeSetClipboard
		[ThreadStatic] static int SafeSetClipboardDataVersion;
		
		static void SafeSetClipboard(object dataObject)
		{
			// Work around ExternalException bug. (SD2-426)
			// Best reproducable inside Virtual PC.
			int version = unchecked(++SafeSetClipboardDataVersion);
			try {
				Clipboard.SetDataObject(dataObject, true);
			} catch (ExternalException) {
				Timer timer = new Timer();
				timer.Interval = 100;
				timer.Tick += delegate {
					timer.Stop();
					timer.Dispose();
					if (SafeSetClipboardDataVersion == version) {
						try {
							Clipboard.SetDataObject(dataObject, true, 10, 50);
						} catch (ExternalException) { }
					}
				};
				timer.Start();
			}
		}

		bool CopyTextToClipboard(string stringToCopy)
		{
			return CopyTextToClipboard(stringToCopy, false);
		}
		
		public void Cut(object sender, EventArgs e)
		{
			if (textArea.SelectionManager.HasSomethingSelected) {
				if (CopyTextToClipboard(textArea.SelectionManager.SelectedText)) {
					if (textArea.SelectionManager.SelectionIsReadonly)
						return;
					// Remove text
					textArea.BeginUpdate();
					textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition;
					textArea.SelectionManager.RemoveSelectedText();
					textArea.EndUpdate();
				}
			} else if (textArea.Document.TextEditorProperties.CutCopyWholeLine) {
				// No text was selected, select and cut the entire line
				int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
				LineSegment lineWhereCaretIs = textArea.Document.GetLineSegment(curLineNr);
				string caretLineText = textArea.Document.GetText(lineWhereCaretIs.Offset, lineWhereCaretIs.TotalLength);
				textArea.SelectionManager.SetSelection(textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset), textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset + lineWhereCaretIs.TotalLength));
				if (CopyTextToClipboard(caretLineText, true)) {
					if (textArea.SelectionManager.SelectionIsReadonly)
						return;
					// remove line
					textArea.BeginUpdate();
					textArea.Caret.Position = textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset);
					textArea.SelectionManager.RemoveSelectedText();
					textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, curLineNr)));
					textArea.EndUpdate();
				}
			}
		}
		
		public void Copy(object sender, EventArgs e)
		{
			if (!CopyTextToClipboard(textArea.SelectionManager.SelectedText) && textArea.Document.TextEditorProperties.CutCopyWholeLine) {
				// No text was selected, select the entire line, copy it, and then deselect
				int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
				LineSegment lineWhereCaretIs = textArea.Document.GetLineSegment(curLineNr);
				string caretLineText = textArea.Document.GetText(lineWhereCaretIs.Offset, lineWhereCaretIs.TotalLength);
				CopyTextToClipboard(caretLineText, true);
			}
		}
		
		public void Paste(object sender, EventArgs e)
		{
			if (!textArea.EnableCutOrPaste) {
				return;
			}
			// Clipboard.GetDataObject may throw an exception...
			for (int i = 0;; i++) {
				try {
					IDataObject data = Clipboard.GetDataObject();
					if (data == null)
						return;
					bool fullLine = data.GetDataPresent(LineSelectedType);
					if (data.GetDataPresent(DataFormats.UnicodeText)) {
						string text = (string)data.GetData(DataFormats.UnicodeText);
                        // we got NullReferenceExceptions here, apparently the clipboard can contain null strings
                        if (!string.IsNullOrEmpty(text))
                        {
                            textArea.Document.UndoStack.StartUndoGroup();
                            try
                            {
                                //多行同步编辑
                                if (textArea.SelectionManager.IsMutilSelect)
                                {
                                    int max = 0; int posIndex = -1;
                                    string selectedText = (string)data.GetData(MuiltSelectedType);
                                    if (string.IsNullOrEmpty(selectedText))
                                    {
                                        selectedText = text;                                       
                                    }
                                    posIndex = selectedText.IndexOf("__maxLength__=");
                                    if (posIndex != -1)
                                    {
                                        max = Convert.ToInt32(selectedText.Substring(posIndex + "__maxLength__=".Length));
                                        selectedText = selectedText.Substring(0, posIndex - System.Environment.NewLine.Length);
                                    }
                                    else
                                    {
                                        max = selectedText.Length;
                                    }
                                    string[] selectedStrings = System.Text.RegularExpressions.Regex.Split(selectedText, System.Environment.NewLine);

                                    if (textArea.SelectionManager.HasSomethingMutilLinesSeleted)
                                    {                                        
                                        ICSharpCode.TextEditor.Actions.Delete.DeleteSelection(textArea);
                                    }

                                    char[] destArray = new char[max];
                                    for (int next = 0; next < textArea.SelectionManager.selectionCollection.Count; next++)
                                    {
                                        if (next < selectedStrings.Length)
                                        {
                                            ArrayUtility.Clear(destArray, (char)32);
                                            char[] sorceArray = selectedStrings[next].ToCharArray();
                                            Array.Copy(sorceArray, destArray, sorceArray.Length);
                                        }
                                        string lineSelectedText = new string(destArray);

                                        if (selectedStrings.Length == 1)
                                        {
                                            lineSelectedText = selectedStrings[0];
                                        }

                                        ISelection selection = textArea.SelectionManager.selectionCollection[next];

                                        int startOffset = textArea.Document.PositionToOffset(selection.StartPosition);
                                        int endOffset = textArea.Document.PositionToOffset(selection.EndPosition);
                                        int careOffset = textArea.Document.PositionToOffset(selection.StartPosition);

                                        textArea.Caret.Position = selection.StartPosition;
                                        textArea.InsertString(lineSelectedText, true);

                                        int offsetPos = textArea.Document.PositionToOffset(textArea.Caret.Position) - careOffset;
                                        startOffset = startOffset + offsetPos;
                                        endOffset = endOffset + offsetPos;

                                        selection.StartPosition = textArea.Document.OffsetToPosition(startOffset);
                                        selection.EndPosition = textArea.Document.OffsetToPosition(endOffset);

                                    }                                    
                                }
                                else
                                {
                                    if (textArea.SelectionManager.HasSomethingSelected)
                                    {
                                        textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition;
                                        textArea.SelectionManager.RemoveSelectedText();
                                    }
                                    if (fullLine)
                                    {
                                        int col = textArea.Caret.Column;
                                        textArea.Caret.Column = 0;
                                        if (!textArea.IsReadOnly(textArea.Caret.Offset))
                                            textArea.InsertString(text);
                                        textArea.Caret.Column = col;
                                    }
                                    else
                                    {
                                        // textArea.EnableCutOrPaste already checked readonly for this case
                                        textArea.InsertString(text);
                                    }
                                }
                            }
                            finally
                            {
                                textArea.Document.UndoStack.EndUndoGroup();
                            }
                        }
					}
					return;
				} catch (ExternalException) {
					// GetDataObject does not provide RetryTimes parameter
					if (i > 5) throw;
				}
			}
		}
		
		public void Delete(object sender, EventArgs e)
		{
			new ICSharpCode.TextEditor.Actions.Delete().Execute(textArea);
		}
		
		public void SelectAll(object sender, EventArgs e)
		{
			new ICSharpCode.TextEditor.Actions.SelectWholeDocument().Execute(textArea);
		}
		
		protected virtual void OnCopyText(CopyTextEventArgs e)
		{
			if (CopyText != null) {
				CopyText(this, e);
			}
		}
		
		public event CopyTextEventHandler CopyText;
	}
	
	public delegate void CopyTextEventHandler(object sender, CopyTextEventArgs e);
	public class CopyTextEventArgs : EventArgs
	{
		string text;
		
		public string Text {
			get {
				return text;
			}
		}
		
		public CopyTextEventArgs(string text)
		{
			this.text = text;
		}
	}
}
